﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace Behavioral.Tests
{
    [TestFixture]
    public class ConsoleOutputTests
    {
        private StringWriter redirectedConsoleOutput;
        
        [SetUp]
        public void RedirectConsoleOutput()
        {
            this.redirectedConsoleOutput = new StringWriter();
            Console.SetOut(this.redirectedConsoleOutput);
        }

        public string[] GetConsoleOutputLines()
        {
            return redirectedConsoleOutput.GetStringBuilder().ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
        }

        #region ForAnEmptyTestItShouldOutputTheClassNameAndMethodName

        [Test]
        public void ForAnEmptyTestItShouldOutputTheClassNameAndMethodName()
        {
            var testClass = new EmptyTest();
            testClass.DoNothing();

            var outputLines = GetConsoleOutputLines();

            Assert.IsNotNull(outputLines);
            Assert.GreaterOrEqual(outputLines.Length, 1);
            Assert.AreEqual("'Empty test'", outputLines[0]);
        }

        class EmptyTest : UnitTest<int>
        {
            public void DoNothing()
            {
                
            }
        }

        #endregion

        #region ForASingleInitializerTestItShouldAppendTheNaturalLanguageSentenceOfTheClassName

        [Test]
        public void ForASingleInitializerTestItShouldAppendTheNaturalLanguageSentenceOfTheClassName()
        {
            var testClass = new TestWithSingleInitializer();
            testClass.SetUpSingleInitializer();

            var outputLines = GetConsoleOutputLines();

            Assert.IsNotNull(outputLines);
            Assert.GreaterOrEqual(outputLines.Length, 2);
            Assert.AreEqual("'Test with single initializer'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
        }

        class TestWithSingleInitializer : UnitTest<int>
        {
            public void SetUpSingleInitializer()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>();
            }
        }

        class ATriviallySimpleInitializerIsSetUp : IInitializer
        {
            public void SetUp()
            {
                
            }
        }

        #endregion

        #region ForMultipleChainedInitializersItShouldAppendTheNaturalLanguageSentenceOfTheClassNamesAndTabify

        [Test]
        public void ForMultipleChainedInitializersItShouldAppendTheNaturalLanguageSentenceOfTheClassNamesAndTabify()
        {
            var testClass = new TestWithChainedInitializers();
            testClass.SetUpChainedInitializers();

            var outputLines = GetConsoleOutputLines();

            Assert.IsNotNull(outputLines);
            Assert.GreaterOrEqual(outputLines.Length, 3);
            Assert.AreEqual("'Test with chained initializers'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("\tand a further initializer is set up", outputLines[2]);
        }

        class TestWithChainedInitializers : UnitTest<int>
        {
            public void SetUpChainedInitializers()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                    .And<AFurtherInitializerIsSetUp>();
            }
        }

        class AFurtherInitializerIsSetUp : IInitializer<int>
        {
            public void SetUp(ref int target)
            {
                
            }
        }

        #endregion

        #region ForAnInitializerAndActionItShouldNotTabify

        [Test]
        public void ForAnInitializerAndActionItShouldNotTabify()
        {
            var testClass = new TestWithInitializerAndAction();
            testClass.SetUpInitializerAndAction();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 3);
            Assert.AreEqual("'Test with initializer and action'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("when an executable action is run", outputLines[2]);
        }

        class TestWithInitializerAndAction : UnitTest<int>
        {
            public void SetUpInitializerAndAction()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>();
            }
        }

        class AnExecutableActionIsRun : IAction<int>
        {
            public void Execute(int target)
            {
                
            }
        }

        #endregion

        #region ForChainedInitializersAndAction

        [Test]
        public void ForChainedInitializersAndAction()
        {
            var testClass = new TestWithChainedInitializersAndAction();
            testClass.SetUpChainedInitializersAndAction();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 4);
            Assert.AreEqual("'Test with chained initializers and action'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("\tand a further initializer is set up", outputLines[2]);
            Assert.AreEqual("when an executable action is run", outputLines[3]);
        }        

        class TestWithChainedInitializersAndAction : UnitTest<int>
        {
            public void SetUpChainedInitializersAndAction()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                    .And<AFurtherInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>();
            }
        }

        #endregion

        #region ForInitializerActionAndAssertion

        [Test]
        public void ForInitializerActionAndAssertion()
        {
            var testClass = new TestWithInitializerActionAndAssertion();
            testClass.SetUpInitializerActionAndAssertion();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 4);
            Assert.AreEqual("'Test with initializer action and assertion'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("when an executable action is run", outputLines[2]);
            Assert.AreEqual("then the outcome of the test is expected", outputLines[3]);
        }

        class TestWithInitializerActionAndAssertion : UnitTest<int>
        {
            public void SetUpInitializerActionAndAssertion()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>()
                .Then<TheOutcomeOfTheTestIsExpected>();
            }
        }

        class TheOutcomeOfTheTestIsExpected : IAssertion<int>
        {
            public void Verify(int target)
            {

            }
        }

        #endregion

        #region ForChainedInitializersActionAndAssertion

        [Test]
        public void ForChainedInitializersActionAndAssertion()
        {
            var testClass = new TestWithChainedInitializersActionAndAssertion();
            testClass.SetUpChainedInitializersActionAndAssertion();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 5);
            Assert.AreEqual("'Test with chained initializers action and assertion'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("\tand a further initializer is set up", outputLines[2]);
            Assert.AreEqual("when an executable action is run", outputLines[3]);
            Assert.AreEqual("then the outcome of the test is expected", outputLines[4]);
        }

        class TestWithChainedInitializersActionAndAssertion : UnitTest<int>
        {
            public void SetUpChainedInitializersActionAndAssertion()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                    .And<AFurtherInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>()
                .Then<TheOutcomeOfTheTestIsExpected>();
            }
        }

        #endregion

        #region ForInitializerActionAndChainedAssertions

        [Test]
        public void ForInitializerActionAndChainedAssertions()
        {
            var testClass = new TestWithInitializerActionAndChainedAssertions();
            testClass.SetUpInitializerActionAndChainedAssertions();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 5);
            Assert.AreEqual("'Test with initializer action and chained assertions'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("when an executable action is run", outputLines[2]);
            Assert.AreEqual("then the outcome of the test is expected", outputLines[3]);
            Assert.AreEqual("\tand a further outcome of a test is expected", outputLines[4]);
        }

        #endregion

        class TestWithInitializerActionAndChainedAssertions : UnitTest<int>
        {
            public void SetUpInitializerActionAndChainedAssertions()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>()
                .Then<TheOutcomeOfTheTestIsExpected>()
                    .And<AFurtherOutcomeOfATestIsExpected>();
            }
        }

        class AFurtherOutcomeOfATestIsExpected : IAssertion<int>
        {
            public void Verify(int target)
            {
                
            }
        }

        #region ForChainedInitializersActionAndChainedAssertions

        [Test]
        public void ForChainedInitializersActionAndChainedAssertions()
        {
            var testClass = new TestWithChainedInitializersActionAndChainedAssertions();
            testClass.SetUpChainedInitializersActionAndChainedAssertions();

            var outputLines = GetConsoleOutputLines();

            Assert.GreaterOrEqual(outputLines.Length, 6);
            Assert.AreEqual("'Test with chained initializers action and chained assertions'", outputLines[0]);
            Assert.AreEqual("Given that a trivially simple initializer is set up", outputLines[1]);
            Assert.AreEqual("\tand a further initializer is set up", outputLines[2]);
            Assert.AreEqual("when an executable action is run", outputLines[3]);
            Assert.AreEqual("then the outcome of the test is expected", outputLines[4]);
            Assert.AreEqual("\tand a further outcome of a test is expected", outputLines[5]);
        }

        #endregion

        class TestWithChainedInitializersActionAndChainedAssertions : UnitTest<int>
        {
            public void SetUpChainedInitializersActionAndChainedAssertions()
            {
                GivenThat<ATriviallySimpleInitializerIsSetUp>()
                    .And<AFurtherInitializerIsSetUp>()
                .When<AnExecutableActionIsRun>()
                .Then<TheOutcomeOfTheTestIsExpected>()
                    .And<AFurtherOutcomeOfATestIsExpected>();
            }
        }

    }
}
